﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;

namespace BuildTools
{
    /// <summary>
    /// Provides methods for generating assembly information source files.
    /// </summary>
    public sealed class AssemblyInfoGenerator
    {
        private CodeDomProvider provider;
        private CodeCompileUnit unit;

        private CodeAttributeDeclaration assemblyVersionAttribute;
        private CodeAttributeDeclaration assemblyFileVersionAttribute;
        private CodeAttributeDeclaration clsCompliantAttribute;

        private bool isInitialized;

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyInfoGenerator"/> class.
        /// </summary>
        /// <param name="provider"></param>
        public AssemblyInfoGenerator(CodeDomProvider provider)
        {
            if (provider == null) throw new ArgumentNullException("provider");

            this.provider = provider;
        }

        /// <summary>
        /// Adds the <see cref="CLSCompliantAttribute"/> to the generated assembly information source code.
        /// </summary>
        /// <param name="isCompliant"></param>
        public void AddCLSCompliant(bool isCompliant)
        {
            EnsureInitialized();

            if (clsCompliantAttribute != null)
                throw new InvalidOperationException("The CLSCompliantAttribute declaration has already been added.");

            clsCompliantAttribute = new CodeAttributeDeclaration(
                new CodeTypeReference(typeof(CLSCompliantAttribute)),
                new CodeAttributeArgument(new CodePrimitiveExpression(isCompliant)));

            unit.AssemblyCustomAttributes.Add(clsCompliantAttribute);
        }

        /// <summary>
        /// Adds the <see cref="AssemblyVersionAttribute"/> to the generated assembly information source code.
        /// </summary>
        /// <param name="version"></param>
        public void AddAssemblyVersion(Version version)
        {
            EnsureInitialized();

            if (assemblyVersionAttribute != null) 
                throw new InvalidOperationException("The AssemblyVersionAttribute declaration has already been added.");

            assemblyVersionAttribute = new CodeAttributeDeclaration(
                    new CodeTypeReference(typeof(AssemblyVersionAttribute)),
                    new CodeAttributeArgument(new CodePrimitiveExpression(version.ToString())));

            unit.AssemblyCustomAttributes.Add(assemblyVersionAttribute);
        }

        /// <summary>
        /// Adds the <see cref="AssemblyFileVersionAttribute"/> to the generated assembly information source code.
        /// </summary>
        /// <param name="version"></param>
        public void AddAssemblyFileVersion(Version version)
        {
            EnsureInitialized();

            if (assemblyFileVersionAttribute != null)
                throw new InvalidOperationException("The AssemblyFileVersionAttribute declaration has already been added.");

            assemblyFileVersionAttribute = new CodeAttributeDeclaration(
                    new CodeTypeReference(typeof(AssemblyFileVersionAttribute)),
                    new CodeAttributeArgument(new CodePrimitiveExpression(version.ToString())));
            
            unit.AssemblyCustomAttributes.Add(assemblyFileVersionAttribute);
        }

        /// <summary>
        /// Saves the generated assembly information source code to the specified location.
        /// </summary>
        /// <param name="path"></param>
        public void Save(string path)
        {
            EnsureInitialized();

            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.IndentString = "    ";
            options.BlankLinesBetweenMembers = true;

            using (StreamWriter writer = File.CreateText(path))
                provider.GenerateCodeFromCompileUnit(unit, writer, options);
        }

        private void EnsureInitialized()
        {
            if (!isInitialized)
            {
                // Set up the code compile unit and namespace imports.
                unit = new CodeCompileUnit();
                unit.Namespaces.Add(new CodeNamespace());
                unit.Namespaces[0].Imports.Add(new CodeNamespaceImport("System"));
                unit.Namespaces[0].Imports.Add(new CodeNamespaceImport("System.Reflection"));

                isInitialized = true;
            }
        }
    }
}
